from django.http import HttpResponseNotFound
from django.shortcuts import render_to_response, redirect
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
import hashlib

from shop.models import Order, OrderProduct
from shop.views.cart import show as show_cart
from shop.views.product import list as show_products

PAYMENT_URL = 'http://webcourse.cs.hut.fi/payment/pay/'
PAYMENT_SELLER_ID = 'heinxjpvanhaljoteitti'
PAYMENT_SECRET_KEY = '379de4716253b014c90fd63f9b496571'


MSG_PAYMENT_NOT_FOUND = u'Payment not found: %s'
MSG_PAYMENT_INVALID_STATE = u'Payment (ID=%s) has already been payed.'
MSG_ORDER_CANCELED = u'The order has been canceled.'


@login_required
def show_payment(request, order_id):
    """Continues a checkout flow for a certain order if order_id and user
    match.

    """
    try:
        order = Order.objects.get(status='W', id=order_id, user=request.user)
        return _prepare_payment(request, order)

    except Order.DoesNotExist:
        return HttpResponseNotFound()


@login_required
def show(request):
    """Shows shopping cart contents as an order"""
    output = {'order': request.cart}
    return render_to_response('checkout_confirm.html', output,
        context_instance=RequestContext(request))


@login_required
def create_order(request):
    """Creates a new Order.

    Active Order ids are marked into the user's session in the key 'payment_id'

    """
    # show empty cart if new checkout is attempted with empty cart
    if not request.cart.items:
        return redirect(show_cart)

    # create order
    order = _create_order(request)

    return _prepare_payment(request, order)


def _prepare_payment(request, order):
    # set active payment id in session
    request.session['payment_id'] = str(order.id)

    # generate checksum for payment service
    plaintext = 'pid=%s&sid=%s&amount=%s&token=%s' % (
        order.id,
        PAYMENT_SELLER_ID,
        order.total_price,
        PAYMENT_SECRET_KEY
    )
    md5 = hashlib.md5()
    md5.update(plaintext)
    checksum = md5.hexdigest()

    # empty cart in session
    request.cart.clear()

    # set payment properties and active Order
    output = {
        'payment_url': PAYMENT_URL,
        'pid': order.id,
        'sid': PAYMENT_SELLER_ID,
        'checksum': checksum,
        'success_url': request.build_absolute_uri(
            reverse('shop.views.checkout.callback', kwargs={'result': 'ok'})),
        'cancel_url': request.build_absolute_uri(
            reverse('shop.views.checkout.callback',
                    kwargs={'result': 'cancelpayment'})),
        'error_url': request.build_absolute_uri(
            reverse('shop.views.checkout.callback',
                    kwargs={'result': 'error'})),
        'order': order
    }

    return render_to_response('checkout_payment.html', output,
        context_instance=RequestContext(request))


def _create_order(request):
    """Create a new Order with the status WAITING_PAYMENT"""
    user = request.user

    # Make a copy of user's shipping address so that modifications to user's
    # shipping address does not affect user's old orders.
    shipping_address = user.get_profile().shipping_address.copy()
    shipping_address.save()

    order = Order.objects.create(
        user=user,
        status='W',
        phonenumber=user.get_profile().phonenumber,
        shipping_address=shipping_address
    )
    order.save()

    # add cart items as OrderProducts into Order
    for item in request.cart.items:
        order_product = OrderProduct.objects.create(
            order=order,
            product = item.product,
            quantity = item.quantity,
            unit_price = item.product.price
        )
        order_product.save()

    return order


@login_required
def cancel(request):
    """Cancels and active order"""
    payment_id = request.session.get('payment_id', '-1')

    # no such active order
    if payment_id == '-1':
        return redirect(show_products)

    output = {
        'pid': payment_id,
    }

    # remove active payment
    if 'payment_id' in request.session:
        del request.session['payment_id']

    # remove Order
    try:
        order = Order.objects.get(id=int(payment_id), user=request.user)

        # invalid state, cant remove already payed order
        if order.status != 'W':
            # set mesasge for error page and show error page
            output['msg'] = 'The order has been already payed, payment ID #%s' % payment_id
            return render_to_response('checkout_error.html', output,
                context_instance=RequestContext(request))

        order.delete()
    except Order.DoesNotExist:
        # set mesasge for error page and show error page
        output['msg'] = 'Order does not exist: %s' % payment_id
        return render_to_response('checkout_error.html', output,
            context_instance=RequestContext(request))

    request.flash['notice'] = MSG_ORDER_CANCELED

    return redirect(show_products)


@login_required
def callback(request, result):
    """Callback to be called from payment service"""
    payment_id = request.GET.get('pid')
    ref = request.GET.get('ref')
    checksum = request.GET.get('checksum')

    # set generic details from payment server to output
    output = {
        'pid': payment_id,
        'ref': ref
    }

    # check that payment id from payment server matches the active payment
    active_payment_id = str(request.session.get('payment_id', ''))
    if payment_id != active_payment_id:
        print "invalid payment id in callback (pid:'%s', active payment:'%s')" % (payment_id, active_payment_id)
        output['msg'] = MSG_PAYMENT_NOT_FOUND % payment_id
        return render_to_response('checkout_error.html', output,
            context_instance=RequestContext(request))

    # validate checksum
    plaintext = 'pid=%s&ref=%s&token=%s' % (payment_id, ref, PAYMENT_SECRET_KEY)
    md5 = hashlib.md5()
    md5.update(plaintext)
    calculated_checksum = md5.hexdigest()
    if checksum != calculated_checksum:
        print 'invalid checksum in payment callback (payment_id:%d, payment_reference:%s, checksum:%s), calculated checksum: %s' % (payment_id, ref, checksum, calculated_checksum)
        return render_to_response('checkout_error.html', output,
            context_instance=RequestContext(request))

    # 'OK' from payment server
    if result == 'ok':

        # set order status to PAYED_NOT_DELIVERED
        try:
            order = Order.objects.get(id=payment_id, user=request.user)

            # check for Order in wrong state to begin with
            if order.status != 'W':
                print "invalid order/payment in payment callback (pid:%s, ref:%s). Order isn't in WAITING_PAYMENT state: %s" % (payment_id, ref, order)
                output.update({
                    'msg': MSG_PAYMENT_INVALID_STATE % payment_id,
                    'order': order
                })
                return render_to_response('checkout_error.html', output,
                    context_instance=RequestContext(request))

            # update Order
            order.status = 'P'
            order.save()
        except Order.DoesNotExist:
            print 'invalid order/payment id in payment callback (pid:%s, ref:%s)' % (payment_id, ref)
            return render_to_response('checkout_error.html', output,
                context_instance=RequestContext(request))

        # empty cart in session
        request.cart.clear()

        # remove active payment from session
        if 'payment_id' in request.session:
            del request.session['payment_id']

        output.update({
            'order': order,
        })

        return render_to_response('checkout_complete.html', output,
            context_instance=RequestContext(request))

    # Payment server has canceled order
    if result == 'cancelpayment':
        return cancel(request)

    # An error has occurred, show payment error page along with generic payment details
    else:
        return render_to_response('checkout_error.html', output,
            context_instance=RequestContext(request))


